using System.Collections.Generic;
using UnityEngine;

public class GameBoard : MonoBehaviour
{
   [SerializeField] private Transform ground = default;
   [SerializeField] private GameTile tilePrefab = default;
   [SerializeField] private Texture2D gridTexture = default;
   private Vector2Int m_Size;
   private GameTile[] m_Tiles;
   private Queue<GameTile> searchFrontier = new Queue<GameTile>();
   private List<GameTileContent> updateingContent = new List<GameTileContent>();
   private GameTileContentFactory m_ContentFactory;
   private List<GameTile> spawnPoints = new List<GameTile>();
   private bool showGrid, showPaths;
   private static readonly int MainTex = Shader.PropertyToID("_MainTex");

   public int SpawnPointCount => spawnPoints.Count;
   public bool ShowGrid
   {
      get => showGrid;
      set
      {
         showGrid = value;
         Material m = ground.GetComponent<MeshRenderer>().material;
         if (showGrid)
         {
            m.mainTexture = gridTexture;
            m.SetTextureScale(MainTex, m_Size);
         }
         else
            m.mainTexture = null;
      }
   }
   public bool ShowPaths
   {
      get => showPaths;
      set
      {
         showPaths = value;
         if (showPaths)
            foreach (var tile in m_Tiles)
               tile.ShowPath();
         else
            foreach (var tile in m_Tiles)
               tile.HidePath();
      }
   }
   public void Initialize(Vector2Int size, GameTileContentFactory contentFactory)
   {
      m_Size = size;
      m_ContentFactory = contentFactory;
      ground.localScale = new Vector3(size.x, size.y, 1f);

      var offset = new Vector2((size.x - 1) * 0.5f, (size.y - 1) * 0.5f);
      m_Tiles = new GameTile[size.x * size.y];
      
      for (int i = 0, y = 0; y < size.y; y++)
         for (int x = 0; x < size.x; x++, i++)
         {
            var tile = m_Tiles[i] = Instantiate(tilePrefab, transform, false);
            tile.transform.localPosition = new Vector3(x - offset.x, 0.01f, y - offset.y);

            if (x > 0) GameTile.MakeEastWestNeighbors(tile,m_Tiles[i-1]);
            if (y > 0) GameTile.MakeNorthSouthNeighbors(tile,m_Tiles[i-size.x]);

            tile.IsAlternative = (x & 1) == 0;
            if ((y & 1) == 0)
            {
               tile.IsAlternative = !tile.IsAlternative;
            }
         }
      Clear();
   }

   public void Clear()
   {
      foreach (var tile in m_Tiles) tile.Content = m_ContentFactory.Get(GameTileContentType.Empty);
      spawnPoints.Clear();
      ToggleDestination(m_Tiles[m_Tiles.Length / 2]);
      ToggleSpawnPoint(m_Tiles[0]);
   }

   private bool FindPaths()
   {
      foreach (var tile in m_Tiles)
         if (!tile.HasPath)
            return false;
      
      foreach (GameTile tile in m_Tiles)
         if (tile.Content.Type == GameTileContentType.Destination)
         {
            tile.BecomeDestination();
            searchFrontier.Enqueue(tile);
         }
         else tile.ClearPath();
      
      if (searchFrontier.Count == 0) return false;

      while (searchFrontier.Count > 0)
      {
         var tile = searchFrontier.Dequeue();
         if (tile != null)
         {
            if (tile.IsAlternative)
            {
               searchFrontier.Enqueue(tile.GrowPathNorth());
               searchFrontier.Enqueue(tile.GrowPathSouth());
               searchFrontier.Enqueue(tile.GrowPathEast());
               searchFrontier.Enqueue(tile.GrowPathWest());
            }
            else
            {
               searchFrontier.Enqueue(tile.GrowPathWest());
               searchFrontier.Enqueue(tile.GrowPathEast());
               searchFrontier.Enqueue(tile.GrowPathSouth());
               searchFrontier.Enqueue(tile.GrowPathNorth());
            }
         }
      }

      foreach (var tile in m_Tiles) tile.ShowPath();
      if (showPaths)
         foreach (var tile in m_Tiles)
            tile.ShowPath();
      return true;
   }

   public GameTile GetTile(Ray ray)
   {
      if (Physics.Raycast(ray,out RaycastHit hit,float.MaxValue,1))
      {
         int x = (int)(hit.point.x + m_Size.x * 0.5f);
         int y = (int)(hit.point.z + m_Size.y * 0.5f);
         if (x >= 0 && x < m_Size.x && y >= 0 && y < m_Size.y)
         {
            return m_Tiles[x + y * m_Size.x];
         }
      }
      return null;
   }

   public void GameUpdate()
   {
      for (int i = 0; i < updateingContent.Count; i++)
      {
         updateingContent[i].GameUpdate();
      }
   }
   public void ToggleDestination(GameTile tile)
   {
      if (tile.Content.Type == GameTileContentType.Destination)
      {
         if (!FindPaths())
         {
            tile.Content = m_ContentFactory.Get(GameTileContentType.Empty);
            FindPaths();
         }
      }
      else if (tile.Content.Type == GameTileContentType.Empty)
      {
         tile.Content = m_ContentFactory.Get(GameTileContentType.Destination);
         FindPaths();
      }
   }

   public void ToggleWall(GameTile tile)
   {
      if (tile.Content.Type == GameTileContentType.Wall)
      {
         tile.Content = m_ContentFactory.Get(GameTileContentType.Empty);
         FindPaths();
      }
      else if (tile.Content.Type == GameTileContentType.Empty)
      {
         tile.Content = m_ContentFactory.Get(GameTileContentType.Wall);
         if (!FindPaths())
         {
            tile.Content = m_ContentFactory.Get(GameTileContentType.Empty);
            FindPaths();
         }
      }
   }

   public void ToggleSpawnPoint(GameTile tile)
   {
      if (tile.Content.Type == GameTileContentType.SpawnPoint)
      {
         if (spawnPoints.Count > 1)
         {
            spawnPoints.Remove(tile);
            tile.Content = m_ContentFactory.Get(GameTileContentType.Empty);
         }
      }else if (tile.Content.Type == GameTileContentType.Empty)
      {
         tile.Content = m_ContentFactory.Get(GameTileContentType.SpawnPoint);
         spawnPoints.Add(tile);
      }
   }

   public void ToggleTower(GameTile tile, TowerType towerType)
   {
      if (tile.Content.Type==GameTileContentType.Tower)
      {
         updateingContent.Remove(tile.Content);
         if (((Tower)tile.Content).TowerType == towerType)
         {
            tile.Content = m_ContentFactory.Get(GameTileContentType.Empty);
            FindPaths();
         }
         else
         {
            tile.Content = m_ContentFactory.Get(towerType);
            updateingContent.Add(tile.Content);
         }
      }else if (tile.Content.Type == GameTileContentType.Empty)
      {
         tile.Content = m_ContentFactory.Get(towerType);
         if (FindPaths())
         {
            updateingContent.Add(tile.Content);
         }
         else
         {
            tile.Content = m_ContentFactory.Get(GameTileContentType.Empty);
            FindPaths();
         }
      }else if (tile.Content.Type == GameTileContentType.Wall)
      {
         tile.Content = m_ContentFactory.Get(towerType);
         updateingContent.Add(tile.Content);
      }
   }

   public GameTile GetSpawnPoint(int index)
   {
      return spawnPoints[index];
   }
}
